import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Stack;

enum FuncType {LAMBDA, PRINT, NULL, ISINTEGER, ISTUPLE, 
    ISDUMMY, ISTRUTHVALUE, ISFUNCTION, ISSTRING, CONC,
    ORDER, TUPLE, YSTAR, STEM, ITOS, STERN, ETA, UNKNOWN}

enum ControlType {TUPLE, GENERAL, LAMBDA, GAMMA,
    ENVIRO, BINOPA, BINOPL, UNOP, BETA, TAU, AUG, 
    EMPTY, UNKOWN, ETA}

public class CSEMachine {
    TreeNode stRoot;
    static Stack<Item> C;
    static Stack<Item> S;
    
    static ArrayList<Delta> D;
    static int currDelta;
    static int currEnv;
    static Stack<Item> currC;
    
    
    
    public CSEMachine() {
        currEnv = 0;
        currDelta = 0;
        C = new Stack<Item>();
        S = new Stack<Item>();
        //E = new Stack<Environment>();
        D = new ArrayList<Delta>();
        currC = C;
    }
    
    public void setRoot(TreeNode stRoot) {
        this.stRoot = stRoot;
    }
    
    public void evaluate()
    {
        Environment e = new Environment(null, null, currEnv, null);
        Item pe = new Item(ControlType.ENVIRO);
        pe.val = e;
        S.push(pe);
        C.push(pe);
        
        buildControlStack(stRoot);

        cseRun();
        System.out.print("\n");
        
    }
    

    
    private void buildControlStack(TreeNode stNode)
    {
        if(stNode == null)
            return;
        
        //setType(stNode);
        
        Item it = new Item(ControlType.GENERAL);
        switch(stNode.getType()) {
            case ID:
            case INT:
            case NIL:
            case STR:
            case BOOL:
            case DUMMY:
            case YSTAR:
                it.val  = stNode;
                it.type = ControlType.GENERAL;
                currC.add(it);
                break;
            case LAMBDA:{
                it.val = new Leta(currDelta, stNode.getLeftChild());
                it.type = ControlType.LAMBDA;
                currC.add(it);
                Delta d = new Delta(currDelta ++);
                Stack<Item> tmpC;
                tmpC = currC;
                currC = d.structure;
                buildControlStack(
                        stNode.getRightChild());
                D.add(d);
                currC = tmpC;
                break;
            }
            case NEG:
            case NOT:{
                it.val = stNode;
                it.type = ControlType.UNOP;
                currC.add(it);
                buildControlStack(
                        stNode.getLeftChild());
                break;
            }
            case OR:
            case AND:{
                it.val = stNode;
                it.type = ControlType.BINOPL;
                currC.add(it);
                buildControlStack(
                        stNode.getLeftChild());
                buildControlStack(
                        stNode.getRightChild());
                break;
            }
            case GR:
            case GE:
            case LS:
            case LE:
            case EQ:
            case NE:
            case PLUS:
            case MINUS:
            case MULTI:
            case DIVIDE:
            case EXPO:{
                it.val = stNode;
                it.type = ControlType.BINOPA;
                currC.add(it);
                buildControlStack(stNode.getLeftChild());
                buildControlStack(stNode.getRightChild());
                break;
            }
            case AUG:{
                it.val = stNode;
                it.type = ControlType.AUG;
                currC.add(it);
                buildControlStack(stNode.getLeftChild());
                buildControlStack(stNode.getRightChild());
                break;
            }
            case CONDITIONAL:{
                it.type = ControlType.BETA;
                it.val = new Beta();
                currC.add(it);

                Delta d = new Delta(currDelta ++);
                D.add(d);
                Stack<Item> tmpC = currC;
                currC = d.structure;
                buildControlStack(
                    stNode.getLeftChild().getExtraChild());
                ((Beta)tmpC.get(
                        tmpC.indexOf(it)).val).T = d;
                d = new Delta(currDelta ++);
                D.add(d);
                currC = d.structure;
                buildControlStack(
                    stNode.getLeftChild().getLeftChild().getExtraChild());
                ((Beta)tmpC.get(
                        tmpC.indexOf(it)).val).F = d;
                
                currC = tmpC;
                buildControlStack(
                        stNode.getExtraChild());
                break;
            }
            case GAMMA:{
                it.val = stNode;
                it.type = ControlType.GAMMA;
                currC.add(it);
                buildControlStack(stNode.getLeftChild());
                buildControlStack(stNode.getRightChild());
                break;
            }
            case TAU:{
                it.type = ControlType.TAU;
                it.val = new Tau();
                int counter = 0;
                currC.add(it);
                while(stNode != null) {
                    counter ++;
                    buildControlStack(
                            stNode.getExtraChild());
                    stNode = stNode.getLeftChild();
                }
                
                int i = currC.indexOf(it);
                ((Tau)currC.get(i).val).number = counter;
                break;
            }
            default:
                try {
                    throw new Exception("Unkown Node Type");
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }
        return;
    }
    
    public void cseRun() {
        while(!C.isEmpty()) {
            Item control = C.peek();
            switch(control.type) {
            case TUPLE:
            case GENERAL: rule1(); break;
            case LAMBDA: rule2(); break;
            case GAMMA: rule3(); break;
            case ENVIRO: rule5(); break;
            case BINOPA:
            case BINOPL: rule6(); break;
            case UNOP: rule7(); break;
            case BETA: rule8(); break;
            case TAU: rule9(); break;
            case AUG: aug(); break;
            default:
                try {
                    throw new Exception("Unknow Type in Run");
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
   public Item lookUp(Item c){
       Environment env = Environment.find();
       while(env != null) {
           if(env.from == null)
               break;
           if(((TreeNode)env.from.val).name.equals(((TreeNode)c.val).name)) {
               c = env.to;
               return c;
           }
           env = env.prev;
           
       }
       return c;
   }
    
    //Stack item
    private void rule1() {
//        System.out.println("rule1");
        Item tmp = C.pop();
        if (getTrueType(tmp).equals("id"))
            tmp = lookUp(tmp);
        S.add(tmp);
    }
   
    //remove control reference
    private void rule2() {
//        System.out.println("rule2");
        Item lambda = C.pop();
        ((Leta)lambda.val).c = Environment.find();
        S.push(lambda);
    }
    
    //apply gamma controls
    private void rule3(){
//        System.out.println("rule3");
        Item c = C.pop();
        FuncType f = funcCheck(S.peek());
        switch(f) {
        case LAMBDA: rule4(); break;//Lambda replacement
        case PRINT: printTop(); break;//Print the top item in Stack
        case NULL: 
        case ISTUPLE:
        case ISDUMMY:
        case ISTRUTHVALUE:
        case ISFUNCTION:
        case ISSTRING:
        case ISINTEGER: is(); break;
        case CONC: conc(); break;
        case ORDER: order(); break;
        case TUPLE: rule10(); break;
        case YSTAR: rule12(); break;
        case STEM: stem(); break;
        case ITOS: itos(); break;
        case STERN: stern(); break;
        case ETA: rule13(); break;
        default:
            try {
                if(S.peek().type == ControlType.GENERAL)
                    throw new Exception("UnknowFunction");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } 
        }
    }
    
    //apply lambda
    private void rule4() {
//        System.out.println("rule4");
        Item lambda = S.pop();
        Item tempI;
        Environment closeEnv = ((Leta)lambda.val).c;
        
        if(((TreeNode)((Leta)lambda.val).x.val).getType() == NodeType.COMMA) {
            Item tuple = S.pop();
            if(tuple.type != ControlType.TUPLE)
                try {
                    throw new Exception("Fail to match tuple after comma");
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } 
                
            TreeNode tempT = (TreeNode)((Leta)lambda.val).x.val;
            LinkedList<Item> TupleItem = ((Tuple)tuple.val).storage;
            while (tempT != null) {
                currEnv ++;
                tempI = new Item(new TreeNode(NodeType.ID,
                        tempT.getExtraChild().name));
                closeEnv = new Environment(
                        tempI, 
                        TupleItem.poll(), 
                        currEnv,
                        closeEnv);
                Item env = new Item(ControlType.ENVIRO);
                env.val = closeEnv;
                S.push(env);
                C.push(env);
                tempT = tempT.getLeftChild();
            }
        }
        else {
            currEnv ++;
            closeEnv = new Environment(
                    ((Leta)lambda.val).x, 
                    S.pop(),
                    currEnv,
                    closeEnv);
            Item env = new Item(ControlType.ENVIRO);
            env.val = closeEnv;
            S.push(env);
            C.push(env);
        }

        (Delta.find(((Leta)lambda.val).k)).putOnC();
    }
    

    //exit environment
    private void rule5() {
//        System.out.println("rule5");
        C.pop();
        if(S.peek().type == ControlType.ENVIRO) {
            S.pop();
        }
        else {
            int i = S.size() - 1;
            while(i >= 0 && S.get(i).type != ControlType.ENVIRO)
                i --;
            if (i != 0)
                S.remove(i);
        }
    }
    
    //Binops calculation
    private void rule6(){
//        System.out.println("rule6");
        Item binop = C.pop();
        Item rand1 = S.pop();
        Item rand2 = S.pop();
        if (binop.type == ControlType.BINOPA) {
            if (((TreeNode)rand1.val).getType() == NodeType.INT
                    && ((TreeNode)rand2.val).getType() == NodeType.INT) {
                int i1, i2 = 0;
                i1 = Integer.parseInt(((TreeNode)rand1.val).stringValue);
                i2 = Integer.parseInt(((TreeNode)rand2.val).stringValue);
                switch(((TreeNode)binop.val).getType()) {
                case PLUS: S.push(
                        new Item(
                                new TreeNode(NodeType.INT, 
                                        Integer.toString(i1 + i2))));
                     break;
                case MINUS: S.push(
                        new Item(
                                new TreeNode(NodeType.INT, 
                                        Integer.toString(i1 - i2))));
                     break;
                case MULTI: S.push(
                        new Item(
                                new TreeNode(NodeType.INT, 
                                        Integer.toString(i1 * i2))));
                     break;
                case DIVIDE: S.push(
                        new Item(
                                new TreeNode(NodeType.INT, 
                                        Integer.toString(i1 / i2))));
                     break;
                case EXPO: S.push(
                        new Item(
                                new TreeNode(NodeType.INT, 
                                        Integer.toString((int)Math.pow(i1, i2)))));
                     break;
                case GR: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        i1 > i2? "true" : "false" )));
                     break;
                case GE: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        i1 >= i2? "true" : "false" )));
                     break;
                case LS: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        i1 < i2? "true" : "false" )));
                     break;
                case LE: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        i1 <= i2? "true" : "false" )));
                     break;
                case EQ: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        i1 == i2? "true" : "false" )));
                     break;
                case NE: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        i1 != i2? "true" : "false" )));
                     break;
                default:
                    try {
                        throw new Exception("Unknown BINOPA");
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
            else if(((TreeNode)rand1.val).getType() == NodeType.STR
                    && ((TreeNode)rand2.val).getType() == NodeType.STR){ //string comparison
                
                String s1 = ((TreeNode)rand1.val).stringValue;
                String s2 = ((TreeNode)rand2.val).stringValue;
                switch(((TreeNode)binop.val).getType()) {
                case GR: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        s1.compareTo(s2) > 0? "true" : "false" )));
                     break;
                case GE: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        s1.compareTo(s2) >= 0? "true" : "false" )));
                     break;
                case LS: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        s1.compareTo(s2) < 0? "true" : "false" )));
                     break;
                case LE: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        s1.compareTo(s2) <= 0? "true" : "false" )));
                     break;
                case EQ: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        s1.compareTo(s2) == 0? "true" : "false" )));
                     break;
                case NE: S.push(
                        new Item(
                                new TreeNode(NodeType.BOOL, 
                                        s1.compareTo(s2) != 0? "true" : "false" )));
                     break;
                }
            } else
                try {
                    throw new Exception("Incompatiabel type:" + 
                            ((TreeNode)rand1.val).getType().toString() + "," + //rand1 type;
                            ((TreeNode)rand2.val).getType().toString());//rand2 type
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }
        else {
            boolean b1 = Boolean.parseBoolean(((TreeNode)rand1.val).stringValue);
            boolean b2 = Boolean.parseBoolean(((TreeNode)rand2.val).stringValue);
            switch(((TreeNode)binop.val).getType()) {
            case OR:S.push(
                    new Item(
                            new TreeNode(NodeType.BOOL, 
                                    Boolean.toString(b2 || b1) )));
                 break;
            case AND:S.push(
                    new Item(
                            new TreeNode(NodeType.BOOL, 
                                    Boolean.toString(b2 && b1) )));
                 break;
            }
        }
    }
    
    //Unop calculation
    private void rule7() {
//        System.out.println("rule7");
        Item unop = C.pop();
        if (((TreeNode)unop.val).getType() == NodeType.NOT) {
            ((TreeNode)S.peek().val).boolValue = 
                !((TreeNode)S.peek().val).boolValue;
            
            ((TreeNode)S.peek().val).stringValue = 
                Boolean.toString(((TreeNode)S.peek().val).boolValue);
            
            ((TreeNode)S.peek().val).name = ((TreeNode)S.peek().val).stringValue;
        }
        else {
            ((TreeNode)S.peek().val).intValue = 
                -((TreeNode)S.peek().val).intValue;
            
            ((TreeNode)S.peek().val).stringValue = 
                Integer.toString(((TreeNode)S.peek().val).intValue);
            
            ((TreeNode)S.peek().val).name = ((TreeNode)S.peek().val).stringValue;
        }
        
    }
    
    //Conditional
    private void rule8() {
//        System.out.println("rule8");
        Item tf = S.pop();
        Item b = C.pop();
        if(((TreeNode)tf.val).getType() != NodeType.BOOL)
            try {
                throw new Exception("Conditional on non-bool");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        
        if (((TreeNode)tf.val).boolValue) {
            ((Delta)((Beta)b.val).T).putOnC();
        }
        else {
            ((Delta)((Beta)b.val).F).putOnC();
        }
    }
    
    //tuple formation
    private void rule9() {
//        System.out.println("rule9");
        Item tau = C.pop();
        Item tuple = new Item(ControlType.TUPLE);
        tuple.val = new Tuple();
        ((Tuple)tuple.val).storage = new LinkedList<Item>();
        LinkedList<Item> list = ((Tuple)tuple.val).storage;
        int num = ((Tau)tau.val).number;
        
        while(num > 0) {
            list.add(S.pop());
            num --;
        }
        ((Tuple)tuple.val).number = ((Tau)tau.val).number;
        S.push(tuple);
    }
    
    //tuple selection
    private void rule10() {
//        System.out.println("rule1");
        Item tuple = S.pop();
        if (((TreeNode)S.peek().val).getType() != NodeType.INT)
            try {
                throw new Exception("Invalid tuple selection");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
       
        int val = ((TreeNode)S.pop().val).intValue;
        //if (((Tuple)tuple.val).storage.size() > 0)
        S.push(((Tuple)tuple.val).storage.get(val - 1));
    }
    
    private void rule12() {
//        System.out.println("rule12");
        S.pop();//Ystar
        Item l = S.pop();//lambda
        Item eta = new Item(ControlType.ETA);
        eta.val = new Leta();
        ((Leta)eta.val).k = ((Leta)l.val).k;
        ((Leta)eta.val).c = ((Leta)l.val).c;
        ((Leta)eta.val).x = ((Leta)l.val).x;
        S.push(eta);
    }

    private void rule13() {
//        System.out.println("rule13");
        Item g = new Item(ControlType.GAMMA);
        Item l = new Item(ControlType.LAMBDA);
        l.val = new Leta();
        ((Leta)l.val).c = ((Leta)S.peek().val).c;
        ((Leta)l.val).k = ((Leta)S.peek().val).k;
        ((Leta)l.val).x = ((Leta)S.peek().val).x;
        C.push(g);
        C.push(g);
        S.push(l);
        
    }
    
    private void aug() {
        String type = getTrueType(S.peek());
        if (!type.equals("tuple") && !type.equals("nil"))
            try {
                throw new Exception("Augment on none tuple element");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
        C.pop();
        if (type.equals("tuple")) { //already existed tuple
            Item t = S.pop();
            ((Tuple)t.val).number ++;
            ((Tuple)t.val).storage.add(S.pop());
            S.push(t);
        }
        else { // new tuple
            Item tuple = new Item(ControlType.TUPLE);
            tuple.val = new Tuple();
            ((Tuple)tuple.val).number = 1;
            S.pop();
            ((Tuple)tuple.val).storage.add(S.pop());
            S.push(tuple);
        }
    }

    private void printTop() {
        S.pop();
        if (S.peek().type == ControlType.GENERAL) {
            ((TreeNode)S.peek().val).stringValue = ((TreeNode)S.peek().val).stringValue.replace("\\t", "\t");
            ((TreeNode)S.peek().val).stringValue = ((TreeNode)S.peek().val).stringValue.replace("\\n", "\n");
            switch(((TreeNode)S.peek().val).getType()) {
            case INT:
                System.out.print(((TreeNode)S.peek().val).intValue);break;
            case STR:
                System.out.print(((TreeNode)S.peek().val).stringValue);break;
            case BOOL:
                System.out.print(((TreeNode)S.peek().val).boolValue);break;
            case DUMMY:
                System.out.print("dummy");
            case NIL:
                System.out.print("nil");
            default:
                System.out.print(((TreeNode)S.peek().val).stringValue);break;
            }
        }
        else if (S.peek().type == ControlType.ETA) {
            System.out.print("[eta closure: " + 
                    ((TreeNode)((Leta)S.peek().val).x.val).name + ": " + //x name
                    ((Leta)S.peek().val).k + "]");// # of eta
        }
        else if( S.peek().type == ControlType.LAMBDA) {
            System.out.print("[lambda closure: " + 
                    ((TreeNode)((Leta)S.peek().val).x.val).name + ": " +//x name
                    (((Leta)S.peek().val).k + 1) + "]");// # of eta
        }
        else {
            System.out.print("(");
            Item tmp = S.peek();
            Iterator<Item> i = ((Tuple)tmp.val).storage.iterator();
            while(i.hasNext()) {
                System.out.print(((TreeNode)i.next().val).stringValue);
                if(i.hasNext())
                    System.out.print(", ");
            }
            System.out.print(")");
        }
        S.pop();
        TreeNode newNode = new TreeNode(
                NodeType.DUMMY, "dummy");
        Item dummy = new Item(newNode);
        S.push(dummy);
    }
    
    private void conc() {
        C.pop();
        S.pop();
        Item rand1 = S.pop();
        Item rand2 = S.pop();
        Item res = new Item(
                        new TreeNode(
                                NodeType.STR, 
                                ((TreeNode)rand1.val).stringValue + 
                                ((TreeNode)rand2.val).stringValue));
        S.push(res);
    }
    
    private void order() {
        S.pop();
        Item tuple = S.pop();
        if(getTrueType(tuple).equals("nil")) {
            TreeNode newNode = new TreeNode(
                    NodeType.INT, "0");
            Item newItem = new Item(newNode);
            S.push(newItem);
        }
        else {
            TreeNode newNode = new TreeNode(
                    NodeType.INT, Integer.toString(
                            ((Tuple)tuple.val).number));
            Item newItem = new Item(newNode);
            S.push(newItem);
        }
    }
    
    private void stern() {
        S.pop();
        String s = ((TreeNode)S.pop().val).stringValue;
        S.push(new Item(
                new TreeNode(NodeType.STR, s.substring(1))));
       
    }

    private void itos() {
        S.pop();
        int i = ((TreeNode)S.pop().val).intValue;
        S.push(
                new Item(
                        new TreeNode(NodeType.STR, Integer.toString(i))));
        
    }

    private void stem() {
        S.pop();
        String s = ((TreeNode)S.pop().val).stringValue;
        S.push(new Item(
                new TreeNode(NodeType.STR, s.substring(0,1))));
    }

    private void is() {
        FuncType f = funcCheck(S.pop());
        Item tmp = S.pop();
        String type = getTrueType(tmp);
        
        Item tr = new Item(
                new TreeNode(NodeType.BOOL, "true"));
        
        
        Item fa = new Item(
                new TreeNode(NodeType.BOOL, "false"));
        
        switch(f) {
        case ISINTEGER: 
            if (type.equals("int"))
                S.push(tr);
            else
                S.push(fa);
            break;
        case ISTUPLE:
            if (type.equals("tuple") || type.equals("nil"))
                S.push(tr);
            else
                S.push(fa);
            break;
        case NULL: 
            if (type.equals("nil"))
                S.push(tr);
            else
                S.push(fa);
            break;
        case ISDUMMY:
            if (type.equals("dummy"))
                S.push(tr);
            else
                S.push(fa);
            break;
        case ISTRUTHVALUE:
            if (type.equals("bool"))
                S.push(tr);
            else
                S.push(fa);
            break;
        case ISFUNCTION:
            if (type.equals("eta") ||
                    type.equals("lambda"))
                S.push(tr);
            else
                S.push(fa);
            break;
        case ISSTRING:
            if (type.equals("str"))
                S.push(tr);
            else
                S.push(fa);
            break;
        }
    }
   

    private FuncType funcCheck(Item i) {
       switch((ControlType)i.type) {
       case  LAMBDA:
           return FuncType.LAMBDA;
       case  ETA:
           return FuncType.ETA;
       case TUPLE:
           return FuncType.TUPLE;
       case GENERAL:
           if(((TreeNode)i.val).getType() == NodeType.ID) {
               String name = 
                   ((TreeNode)i.val).name.toLowerCase().trim();
               if (name.equals("null")) 
                   return FuncType.NULL;
               if (name.equals("conc")) return FuncType.CONC;
               if (name.equals("itos")) return FuncType.ITOS;
               if (name.equals("stem")) return FuncType.STEM;
               if (name.equals("print")) return FuncType.PRINT;
               if (name.equals("order")) return FuncType.ORDER;
               if (name.equals("stern")) return FuncType.STERN;
               if (name.equals("istuple")) return FuncType.ISTUPLE;
               if (name.equals("isdummy")) return FuncType.ISDUMMY;
               if (name.equals("isstring")) return FuncType.ISSTRING;
               if (name.equals("isinteger")) return FuncType.ISINTEGER;
               if (name.equals("isfunction")) return FuncType.ISFUNCTION;
               if (name.equals("istruthvalue")) return FuncType.ISTRUTHVALUE;
           }
           if (((TreeNode)i.val).getType() == NodeType.YSTAR)
               return FuncType.YSTAR;
       }
       return FuncType.UNKNOWN;
   }
    
    //return a lowcase type name string
    private String getTrueType(Item i) {
        switch (i.type) {
        case GENERAL:
        case UNOP: 
        case BINOPA:
        case BINOPL: return ((TreeNode)i.val).getType().toString().toLowerCase();
        default: return i.type.toString().toLowerCase();
        }
    }
    
    public static class Environment extends ControlBase{
        Item from;
        Item to;
        Environment prev;
        NextEnv next;
        int number;
        
        class NextEnv {
            Environment curr;
            NextEnv next;
            
            public NextEnv() {}
        }

        public Environment() {
            this.from = null;
            this.to = null;
            number = 0;
        }
        
        public Environment(Item from, Item to, int num, Environment prev) {
            this.from = from;
            this.to = to;
            this.number = num;
            this.prev = prev;
        }
        
            
        public static Environment find() {
            
            int ii = S.size();
            Item[] i = 
                S.toArray(new Item[ii]);
            while (ii -- >= 0) {
                if (i[ii].type == ControlType.ENVIRO) 
                    return (Environment)i[ii].val;
            }
            return null;
            
        }
        
    }
    
    class Leta extends ControlBase{
        int k;
        Environment c;
        Item x;
        public Leta(int i, TreeNode st)
        {
            Item it = new Item(st);
            k = i;
            x = it;
            c = null;
        }
        
        public Leta() {};
    }
    
    class Tuple extends ControlBase {
        int number;
        LinkedList<Item> storage;
        public Tuple() {
            storage = new LinkedList<Item> ();
            number = 0;
        }
    }
    
    class Beta extends ControlBase {
        Delta T;
        Delta F;
        public Beta() {}
    }
    
    static class Delta extends ControlBase {
        int number;
        Stack<Item> structure;
        
        public Delta(int num) {
            this.structure = new Stack<Item>();
            number = num;
        }
        
        public static Delta find(int i) {
            Iterator<Delta> it = D.iterator();
            Delta delta;
            while(it.hasNext()) {
                if((delta = it.next()).number == i)
                    return delta;
            }
            try {
                throw new Exception("Unable to locate delta");
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }
        
        public void putOnC() {
            int i = 0;
            while( i < structure.size()) {
                C.push(structure.get(i));
                i ++;
            }
        }
    }
    
    class Tau extends ControlBase {
        int number;
    }
    
    class Item {
        ControlType type;
        ControlBase val;
        
        public Item() {}
        
        public Item(ControlType type) {
            this.type = type;
        }
        
        public Item(TreeNode node) {
            this.type = ControlType.GENERAL;
            this.val = node;
        }
        public String getTypeString() {
            switch (this.type) {
            case GENERAL:
            case UNOP: 
            case BINOPA:
            case BINOPL: return (((TreeNode)this.val).getType().toString());
            default: return(type.toString());
         }
        }
    }
}
